// 版权所有2010 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 包html提供转义和取消转义html文本的功能。
package html

import (
	"strings"
	"unicode/utf8"
)

// 这些替换允许与旧的数字实体兼容，
// 采用Windows-1252编码。
// https:
var replacementTable = [...]rune{
	'\u20AC', // 第一个条目应替换为0x80。
	'\u0081',
	'\u201A',
	'\u0192',
	'\u201E',
	'\u2026',
	'\u2020',
	'\u2021',
	'\u02C6',
	'\u2030',
	'\u0160',
	'\u2039',
	'\u0152',
	'\u008D',
	'\u017D',
	'\u008F',
	'\u0090',
	'\u2018',
	'\u2019',
	'\u201C',
	'\u201D',
	'\u2022',
	'\u2013',
	'\u2014',
	'\u02DC',
	'\u2122',
	'\u0161',
	'\u203A',
	'\u0153',
	'\u009D',
	'\u017E',
	'\u0178', // 最后一项是0x9F。
	// 0x00->“\uFFFD”以编程方式处理。
	// 0x0D->'\u000D'是不可操作的。
}

// unescapeEntity读取的实体类似于“&lt；”从b[src:]将
// 对应的“<”写入b[dst:]，返回递增的dst和src游标。
// 先决条件：b[src]=='&'&dst<=src。
func unescapeEntity(b []byte, dst, src int) (dst1, src1 int) {
	const attribute = false

	// http:

	// 我从1开始，因为我们已经知道s[0]=='&'。
	i, s := 1, b[src:]

	if len(s) <= 1 {
		b[dst] = b[src]
		return dst + 1, src + 1
	}

	if s[i] == '#' {
		if len(s) <= 3 { // 我们至少需要有“&#”。
			b[dst] = b[src]
			return dst + 1, src + 1
		}
		i++
		c := s[i]
		hex := false
		if c == 'x' || c == 'X' {
			hex = true
			i++
		}

		x := '\x00'
		for i < len(s) {
			c = s[i]
			i++
			if hex {
				if '0' <= c && c <= '9' {
					x = 16*x + rune(c) - '0'
					continue
				} else if 'a' <= c && c <= 'f' {
					x = 16*x + rune(c) - 'a' + 10
					continue
				} else if 'A' <= c && c <= 'F' {
					x = 16*x + rune(c) - 'A' + 10
					continue
				}
			} else if '0' <= c && c <= '9' {
				x = 10*x + rune(c) - '0'
				continue
			}
			if c != ';' {
				i--
			}
			break
		}

		if i <= 3 { // 没有匹配的字符。
			b[dst] = b[src]
			return dst + 1, src + 1
		}

		if 0x80 <= x && x <= 0x9F {
			// 将Windows-1252中的字符替换为等效的UTF-8。
			x = replacementTable[x-0x80]
		} else if x == 0 || (0xD800 <= x && x <= 0xDFFF) || x > 0x10FFFF {
			// 用替换字符替换无效字符。
			x = '\uFFFD'
		}

		return dst + utf8.EncodeRune(b[dst:], x), src + i
	}

	// 使用尽可能多的字符，
	// 使用的字符与一个命名引用匹配。

	for i < len(s) {
		c := s[i]
		i++
		// 小写字符在实体中更常见，所以我们首先检查它们。
		if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' {
			continue
		}
		if c != ';' {
			i--
		}
		break
	}

	entityName := s[1:i]
	if len(entityName) == 0 {
		// No-op.
	} else if attribute && entityName[len(entityName)-1] != ';' && len(s) > i && s[i] == '=' {
		// No-op.
	} else if x := entity[string(entityName)]; x != 0 {
		return dst + utf8.EncodeRune(b[dst:], x), src + i
	} else if x := entity2[string(entityName)]; x[0] != 0 {
		dst1 := dst + utf8.EncodeRune(b[dst:], x[0])
		return dst1 + utf8.EncodeRune(b[dst1:], x[1]), src + i
	} else if !attribute {
		maxLen := len(entityName) - 1
		if maxLen > longestEntityWithoutSemicolon {
			maxLen = longestEntityWithoutSemicolon
		}
		for j := maxLen; j > 1; j-- {
			if x := entity[string(entityName[:j])]; x != 0 {
				return dst + utf8.EncodeRune(b[dst:], x), src + j + 1
			}
		}
	}

	dst1, src1 = dst+i, src+i
	copy(b[dst:dst1], b[src:src1])
	return dst1, src1
}

var htmlEscaper = strings.NewReplacer(
	`&`, "&amp;",
	`'`, "&#39;", // ”和#39短于“&apos；”而apos直到HTML5才出现在HTML中。
	`<`, "&lt;",
	`>`, "&gt;",
	`"`, "&#34;", // /“&#34；”比“短”。
)

// EscapeString可以使“<”等特殊字符变成“&lt；”。它
// 只转义五个这样的字符：<，>，，，，，，，.
// UnescapeString（EscapeString（s））==s始终有效，但反之则不是
// 总是正确的。
func EscapeString(s string) string {
	return htmlEscaper.Replace(s)
}

// UnescapeString unescape像“&lt；“变成”<”。它能展现
// 比逃逸逃逸更大范围的实体。例如，&aacute；“
// unescapes to“á”，以及“&#225；“和”&#xE1；“.
// UnescapeString（EscapeString（s））==s始终有效，但相反的不是
// 始终有效。
func UnescapeString(s string) string {
	populateMapsOnce.Do(populateMaps)
	i := strings.IndexByte(s, '&')

	if i < 0 {
		return s
	}

	b := []byte(s)
	dst, src := unescapeEntity(b, i, i)
	for len(s[src:]) > 0 {
		if s[src] == '&' {
			i = 0
		} else {
			i = strings.IndexByte(s[src:], '&')
		}
		if i < 0 {
			dst += copy(b[dst:], s[src:])
			break
		}

		if i > 0 {
			copy(b[dst:], s[src:src+i])
		}
		dst, src = unescapeEntity(b, dst+i, src+i)
	}
	return string(b[:dst])
}
